package com.tiancheng.trade.gateway.auth;

import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.exception.NotPermissionException;
import cn.dev33.satoken.exception.SaTokenException;
import cn.dev33.satoken.reactor.context.SaReactorSyncHolder;
import cn.dev33.satoken.reactor.filter.SaReactorFilter;
import cn.dev33.satoken.router.SaHttpMethod;
import cn.dev33.satoken.router.SaRouter;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import com.alibaba.fastjson.JSON;
import com.tiancheng.admintoken.constant.AuthConstant;
import com.tiancheng.admintoken.utils.StpKit;
import com.tiancheng.trade.gateway.config.IgnoreUrlsConfig;
import com.tiancheng.trade.commom.web.auth.StpCustomKit;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import org.springframework.web.server.ServerWebExchange;

import java.util.*;


/**
 * [Sa-Token 权限认证] 配置类
 *
 * @author click33
 */
@Slf4j
@Configuration
public class SaTokenConfigure {
    /**
     * Redis缓存权限规则（路径->资源）
     */
    String PATH_RESOURCE_MAP = "auth:pathResourceMap";
    @Resource
    private RedisTemplate<String, Object> redisTemplate;


    /**
     * 注册Sa-Token全局过滤器
     */
    @Bean
    public SaReactorFilter getSaReactorFilter(IgnoreUrlsConfig ignoreUrlsConfig) {
        return new SaReactorFilter()
                // 拦截地址
                .addInclude("/**")
                // 配置白名单路径
                .setExcludeList(ignoreUrlsConfig.getUrls())
                // 鉴权方法：每次访问进入
                .setAuth(obj -> {
                    String requestPath = SaHolder.getRequest().getRequestPath();
                    log.info("网关中心请求路径：{}",requestPath);
                    // 对于OPTIONS预检请求直接放行
                    SaRouter.match(SaHttpMethod.OPTIONS).stop();
                    SaRouter.match("/trade-order/open/**", r -> StpCustomKit.OPEN.checkLogin()).stop();
                    SaRouter.match("/trade-merchant/open/**", r -> StpCustomKit.OPEN.checkLogin()).stop();

                    // 一码通:商户端
                    SaRouter.match("/one-code-v2/admin/merchant/**", r -> StpKit.MERCHANT.checkLogin()).stop();
                    // 一码通:平台端
                    SaRouter.match("/one-code-v2/admin/platform/**", r -> StpKit.PLATFORM.checkLogin()).stop();
                    // 一码通:C端
                    SaRouter.match("/one-code-v2/api/c/**", r -> StpKit.CONSUMER.checkLogin()).stop();
                    // 一码通:小程序B端
                    SaRouter.match("/one-code-v2/api/b/**", r -> StpKit.MERCHANT.checkLogin()).stop();

                    // 商家端用户和平台端用户都可以
                    SaRouter.match("/one-code-v2/admin/upload/**", "/one-code-v2/common/**","/one-code-v2/admin/common/**").check(r -> {
                        if (!StpKit.MERCHANT.isLogin() && !StpKit.PLATFORM.isLogin()) {
                            throw new SaTokenException("请登录后再访问接口");
                        }
                    }).stop();


                    // 一码通:小程序B端
                    SaRouter.match("/**", r -> StpKit.PLATFORM.checkLogin());
                    // 获取Redis中缓存的各个接口路径所需权限规则
                    Map<Object, Object> pathResourceMap = new HashMap<>();
                    // 获取当前请求路径
                    String clientId = SaHolder.getRequest().getHeader("X-Request-ClientId");
                    boolean isPlatformReq = requestPath.startsWith("/one-code-v2/admin/platform/")
                            || requestPath.startsWith("/one-code-v2/admin/upload/")
                            || requestPath.startsWith("/one-code-v2/common/");
                    if (isPlatformReq) {
                        pathResourceMap = redisTemplate.opsForHash().entries(AuthConstant.PLATFORM_PATH_RESOURCE_MAP + ":" + clientId);
                    } else if (requestPath.startsWith("/one-code-v2/admin/merchant/")
                            || requestPath.startsWith("/one-code-v2/api/b/")) {
                        pathResourceMap = redisTemplate.opsForHash().entries(AuthConstant.MERCHANT_PATH_RESOURCE_MAP + ":" + clientId);
                    }
                    // 获取到访问当前接口所需权限（一个路径对应多个资源时，拥有任意一个资源都可以访问该路径）
                    List<String> needPermissionList = new ArrayList<>();
                    // 创建路径匹配器
                    PathMatcher pathMatcher = new AntPathMatcher();
                    Set<Map.Entry<Object, Object>> entrySet = pathResourceMap.entrySet();
                    for (Map.Entry<Object, Object> entry : entrySet) {
                        String pattern = (String) entry.getKey();
                        if (pathMatcher.match(pattern, requestPath)) {
                            needPermissionList.add((String) entry.getValue());
                        }
                    }
                    // 接口需要权限时鉴权
                    if (CollUtil.isNotEmpty(needPermissionList)) {
                        if (isPlatformReq) {
                            SaRouter.match(requestPath, r -> StpKit.PLATFORM.checkPermissionOr(Convert.toStrArray(needPermissionList)));
                        } else if (requestPath.startsWith("/one-code-v2/admin/merchant/")
                                || requestPath.startsWith("/one-code-v2/api/b/")) {
                            SaRouter.match(requestPath, r -> StpKit.MERCHANT.checkPermissionOr(Convert.toStrArray(needPermissionList)));
                        }
                    }
                })
                // setAuth方法异常处理
                .setError(this::handleException);
    }

    /**
     * 自定义异常处理
     */
    private String handleException(Throwable e) {
        //设置错误返回格式为JSON
        ServerWebExchange exchange = SaReactorSyncHolder.getContext();
        HttpHeaders headers = exchange.getResponse().getHeaders();
        headers.set("Content-Type", "application/json; charset=utf-8");
        headers.set("Access-Control-Allow-Origin", "*");
        headers.set("Cache-Control", "no-cache");
        log.error("认证授权失败:",e);
        if (e instanceof NotLoginException) {
            return JSON.toJSONString(AuthExceptionResult.fail(403, "未登录"));
        } else if (e instanceof NotPermissionException) {
            return JSON.toJSONString(AuthExceptionResult.fail(405, "无权限操作"));
        } else {
            return JSON.toJSONString(AuthExceptionResult.fail(406, "授权异常"));
        }
    }

}
